/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package phd.dupenois.changeidentifiers.optimisation.singlepattern.frames;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Transparency;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

/**
 *
 * @author Max
 */
public abstract class GraphPanel extends JPanel implements MouseListener,
        MouseWheelListener, MouseMotionListener, ComponentListener {

    private int minX;
    private int minY;
    private int maxX;
    private int maxY;
    private double scaleY = 1;
    private double scaleX = 1;
    private String xAxisTitle;
    private String yAxisTitle;
    private GraphFrame frame;
    private Point bottomLeft;
    private boolean mouseIn;
    private Point dragStart;
    private double zoom;
    private Cursor normalCursor = new Cursor(Cursor.DEFAULT_CURSOR);
    private Cursor dragCursor = new Cursor(Cursor.MOVE_CURSOR);
    final static float dash1[] = {10.0f};
    final static BasicStroke strokeDashed = new BasicStroke(1.5f,
                                      BasicStroke.CAP_BUTT,
                                      BasicStroke.JOIN_MITER,
                                      10.0f, dash1, 0.0f);
    final static BasicStroke strokeNormal = new BasicStroke(1f);
    private class BaselineValue{
        private boolean x;
        private double value;
        public BaselineValue(boolean x, double value){
            this.x = x;
            this.value = value;
        }
        public boolean isX(){return x;}
        public double getValue(){return value;}
        @Override
        public BaselineValue clone(){ return new BaselineValue(x, value);}
    }

    private Vector<BaselineValue> baselines;

    public GraphPanel() {
        this.zoom = 1;
        this.addMouseListener(this);
        this.addMouseWheelListener(this);
        this.addMouseMotionListener(this);
        this.addComponentListener(this);
        this.bottomLeft = new Point(0, 0);
        this.baselines = new Vector<BaselineValue>();

    }

    public void addBaseLine(boolean x, double value){
        this.baselines.add(new BaselineValue(x, value));
    }

    public void setFrame(GraphFrame frame) {
        this.frame = frame;
    }

    public GraphFrame getFrame() {
        return this.frame;
    }

    public Image getImage() {
        Dimension screenDim = getScreenDimensions();
        Image image = createImage(screenDim.width, screenDim.height);
        paint(image.getGraphics());
        return image;
    }

    public void writeImage(File file, String extension) throws IOException {
        BufferedImage image = (BufferedImage) getImage();
        ImageIO.write(image, extension, file);
    }

    public void setXRange(int min, int max) {
        this.minX = min;
        this.maxX = max;
    }

    public void setYRange(int min, int max) {
        this.minY = min;
        this.maxY = max;
    }

    public Point getOrigin() {
        return new Point(20-this.bottomLeft.x, 20-this.bottomLeft.y);
    }

    public Point getActualPosition(int x, int y) {
        return getActualPosition(new Point(x, y));
    }

    public Point getActualPosition(Point point) {
        return new Point(
                (int) Math.ceil((point.x - getOrigin().x) / (getScaleX()*zoom)),
                (int) Math.ceil((getHeight() - point.y - getOrigin().y) / (getScaleY()*zoom)));
    }

    public Point getScreenPosition(int x, int y) {
        return getScreenPosition(new Point(x, y));
    }

    public Point getScreenPosition(Point point) {
        return new Point(
                (int) Math.ceil(getOrigin().x + (point.x * getScaleX()*zoom)),
                (int) Math.ceil(getHeight() - (getOrigin().y + (point.y * getScaleY()*zoom))));
    }

//    public Point getScreenPosition(Point point){
//        return new Point(
//          ((int)Math.round(point.x/zoom))-((int)Math.round(this.bottomLeft.x/zoom)),
//          this.getScreenDimensions().height-(((int)Math.round(point.y/zoom))-((int)Math.round(this.bottomLeft.y/zoom)))
//          );
//    }
//    public Point getActualPosition(Point point){
//        return new Point(
//                (this.bottomLeft.x+(int)Math.round(point.x*zoom)),
//                (int)Math.round((this.getScreenDimensions().height-point.y)*zoom)+this.bottomLeft.y
//                );
//        return new Point(this.bottomLeft.x+point.x, (this.getDimensions().height-point.y)+this.bottomLeft.y);
//    }
    public Dimension getScreenDimensions() {
        int height = (int) Math.ceil((this.getMaxY() * this.getScaleY()*zoom) + 50);//this.getHeight()
        int width = (int) Math.ceil((this.getMaxX() * this.getScaleX()*zoom) + 50);
        return new Dimension(width, height);
    }

    @Override
    public void paint(Graphics g) {
//        super.paint(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setColor(Color.WHITE);
        g2.fillRect(0, 0, getWidth(), getHeight());
        g2.setColor(Color.BLACK);
//        Stroke original = g2.getStroke();
//        g2.setStroke(new BasicStroke(2f));
//        g2.drawRect(0, 0, screenDim.width, screenDim.height);
//        g2.setStroke(original);
        Point origin = getScreenPosition(new Point(0, 0));
        Point maxScreenX = getScreenPosition(new Point(maxX, 0));
        Point maxScreenY = getScreenPosition(new Point(0, maxY));
        g2.drawLine(origin.x, origin.y, maxScreenX.x, maxScreenX.y);
        g2.drawLine(origin.x, origin.y, maxScreenY.x, maxScreenY.y);

        Point origin_text = getScreenPosition(new Point(0, 0));
        g2.drawString("" + getMinX(), origin_text.x - 3, origin_text.y + 13);
        g2.drawString("" + getMinY(), origin_text.x - 13, origin_text.y + 3);

        Point maxX_text = getScreenPosition(new Point(maxX, 0));
        g2.drawString("" + getMaxX(), maxX_text.x, maxX_text.y + 3);

        Point maxY_text = getScreenPosition(new Point(0, (int) Math.ceil(maxY)));
        g2.drawString("" + getMaxY(), maxY_text.x - 3, maxY_text.y - 5);

        Point x_axis_text = getScreenPosition(new Point(maxX / 2, 0));
        g2.drawString(getxAxisTitle(), x_axis_text.x - 20, x_axis_text.y + 13);

        Vector<BaselineValue> baselinesClone = (Vector<BaselineValue>) this.baselines.clone();


        g2.setStroke(strokeDashed);
        g2.setColor(Color.GRAY);
        Point baselineScreenPosition;
        for (BaselineValue baseline : baselinesClone) {
            if (baseline.isX()) {
                baselineScreenPosition = getScreenPosition((int) Math.round(baseline.getValue()), 0);
                g2.drawLine(baselineScreenPosition.x, baselineScreenPosition.y, baselineScreenPosition.x, maxScreenY.y);
                g2.drawString(""+baseline.getValue(), baselineScreenPosition.x-10, baselineScreenPosition.y+20);
            } else {
                baselineScreenPosition = getScreenPosition(0, (int) Math.round(baseline.getValue()));
                g2.drawLine(baselineScreenPosition.x, baselineScreenPosition.y, maxScreenX.x, baselineScreenPosition.y);
                g2.drawString(""+baseline.getValue(), baselineScreenPosition.x-20, baselineScreenPosition.y+5);
            }
        }
        g2.setColor(Color.BLACK);
        g2.setStroke(strokeNormal);
//        AffineTransform previous = g2.getTransform();
//        AffineTransform current = previous;
//        current.rotate(Math.toRadians(90));

        //Back to front because we rotate
        int yTextHeight = g2.getFontMetrics().stringWidth(getyAxisTitle());
        int yTextWidth = g2.getFontMetrics().getHeight();
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();

        GraphicsDevice gs = ge.getDefaultScreenDevice();

        GraphicsConfiguration gc = gs.getDefaultConfiguration();

        Image yAxisTextImage = gc.createCompatibleImage(yTextWidth,yTextHeight, Transparency.BITMASK);
        Graphics2D yGraphics = (Graphics2D)yAxisTextImage.getGraphics();
//        yGraphics.setColor(Color.red);
//        yGraphics.fillRect(0, 0, 100, 100);
        yGraphics.setBackground(new Color(0, 0, 0, 0));
        yGraphics.clearRect(0, 0, yTextWidth, yTextHeight);
        yGraphics.rotate(Math.PI / 2);
        yGraphics.setColor(Color.black);
        yGraphics.drawString(getyAxisTitle(), 0, 0);

        Point y_axis_text = getScreenPosition(new Point((int)(0-yTextWidth/scaleX),
                (int)((Math.ceil(maxY) / 2)+((yTextHeight/scaleY)/2)) ));
        g2.drawImage(yAxisTextImage, y_axis_text.x, y_axis_text.y, yTextWidth, yTextHeight, new Color(0, 0, 0, 0), null);

    }

    /**
     * @return the minX
     */
    public int getMinX() {
        return minX;
    }

    /**
     * @return the minY
     */
    public int getMinY() {
        return minY;
    }

    /**
     * @return the maxX
     */
    public int getMaxX() {
        return maxX;
    }

    /**
     * @return the maxY
     */
    public int getMaxY() {
        return maxY;
    }

    /**
     * @return the scaleY
     */
    public double getScaleY() {
        return scaleY;
    }

    /**
     * @return the scaleX
     */
    public double getScaleX() {
        return scaleX;
    }

    /**
     * @param scaleY the scaleY to set
     */
    public void setScaleY(double scaleY) {
        this.scaleY = scaleY;
    }

    /**
     * @param scaleX the scaleX to set
     */
    public void setScaleX(double scaleX) {
        this.scaleX = scaleX;
    }

    /**
     * @return the xAxisTitle
     */
    public String getxAxisTitle() {
        return xAxisTitle;
    }

    /**
     * @param xAxisTitle the xAxisTitle to set
     */
    public void setxAxisTitle(String xAxisTitle) {
        this.xAxisTitle = xAxisTitle;
    }

    /**
     * @return the yAxisTitle
     */
    public String getyAxisTitle() {
        return yAxisTitle;
    }

    /**
     * @param yAxisTitle the yAxisTitle to set
     */
    public void setyAxisTitle(String yAxisTitle) {
        this.yAxisTitle = yAxisTitle;
    }

    private void zoomBy(double amount) {
        if(this.zoom>=1&&this.zoom+amount < 1){
            this.zoom = 1+(amount*0.1);
        }else if (this.zoom < 1) {
            this.zoom += (amount*0.1);
        }else{
            this.zoom += amount;
        }
        if(this.zoom < 0.1){
            this.zoom = 0.1;
        }
        this.repaint();
    }

    public void mouseClicked(MouseEvent arg0) {
    }

    public void mousePressed(MouseEvent arg0) {
        dragStart = new Point(arg0.getX(), arg0.getY());
        if (mouseIn) {
            setCursor(dragCursor);
        }
    }

    public void mouseReleased(MouseEvent arg0) {
        setCursor(normalCursor);
    }

    public void mouseEntered(MouseEvent arg0) {
        mouseIn = true;

    }

    public void mouseExited(MouseEvent arg0) {
        mouseIn = false;
        setCursor(normalCursor);
    }

    private void moveTo(int x, int y) {
        bottomLeft.move(x, y);
    }

    private void moveBy(int xdiff, int ydiff) {
//        System.out.println("Moving");
        bottomLeft.move(bottomLeft.x - xdiff, bottomLeft.y + ydiff);
    }

    public void mouseDragged(MouseEvent arg0) {
        if (mouseIn) {
            setCursor(dragCursor);
            int xdiff = (int) Math.round((arg0.getX() - dragStart.x) * zoom);
            int ydiff = (int) Math.round((arg0.getY() - dragStart.y) * zoom);
            dragStart = new Point(arg0.getX(), arg0.getY());
            moveBy(xdiff, ydiff);
//            this.listener.setStatus("Bottom Left: ("+bottomLeft.x+","+bottomLeft.y+")");

            this.repaint();
        }
    }

    public void mouseMoved(MouseEvent arg0) {
    }

    public void componentResized(ComponentEvent arg0) {
//        this.footprintPanel.setBounds(0, 0, this.getDimensions().width,this.getDimensions().height);


        this.repaint();
    }

    public void componentMoved(ComponentEvent arg0) {
    }

    public void componentShown(ComponentEvent arg0) {
    }

    public void componentHidden(ComponentEvent arg0) {
    }

    public void mouseWheelMoved(MouseWheelEvent e) {
        zoomBy((double) e.getWheelRotation());
    }
}
